package cn.yangliu.operation.aspect;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.function.BiConsumer;

import javax.servlet.http.HttpServletRequest;

import cn.yangliu.operation.OperationHandler;
import cn.yangliu.operation.annotation.Action;
import cn.yangliu.operation.annotation.Module;
import cn.yangliu.operation.annotation.Operate;
import cn.yangliu.operation.constant.OperationConstant;
import cn.yangliu.operation.entity.Operation;
import cn.yangliu.operation.entity.Operator;
import cn.yangliu.operation.service.OperationService;
import lombok.Lombok;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;


/**
 * @author 杨柳
 * 创建时间 2017-08-25 23:45
 */
@Aspect
@Slf4j
public class OperationAspect implements Ordered {

    /**
     * 切面优先级默认最低
     */
    private int order = Integer.MAX_VALUE;


    @Autowired
    private OperationHandler handler;

    @Autowired
    private OperationService operationService;

    @Pointcut("@annotation(cn.yangliu.operation.annotation.Action)" +
            "||@annotation(cn.yangliu.operation.annotation.Operate)")
    public void advice() {
    }


    /**
     * 创建操作日志
     */
    private Operation createRecord(HttpServletRequest request, ProceedingJoinPoint joinPoint) {
        Operation operation = null;
        /*
         * 操作人Id
         */
        String operatorId;
        /*
         * 操作人名称
         */
        String operatorName;
        /*
         * 操作模块
         */
        String moduleName = null;
        /*
         * 操作行为
         */
        String actionName = null;
        /*
         * 操作等级风险
         */
        String level = null;
        /*
         * 目标调用方法
         */
        String methodName;

        Operator operator = handler.huntOperator(request);

        Object[] args = joinPoint.getArgs();

        Module module = joinPoint.getTarget().getClass().getAnnotation(Module.class);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        methodName = method.getName();
        boolean recordParams = false;
        if (operator != null) {
            operatorId = operator.getOperatorId().toString();
            operatorName = operator.getOperatorName();
        } else {
            operatorId = "0L";
            operatorName = "system";
        }
        //找方法上的的@Operate注解
        Operate operate = method.getDeclaredAnnotation(Operate.class);
        if (operate != null) {
            actionName = operate.action();
            moduleName = operate.module();
            //操作危险级别
            level = operate.operateLevel();
            recordParams = operate.recordParams();
            operation = getInstance(request, operatorId, operatorName, moduleName, actionName, level, methodName);
        } else if (module != null) {
            Action action = method.getDeclaredAnnotation(Action.class);
            if (action != null) {
                //操作危险级别
                level = action.operateLevel();
                moduleName = module.value();
                actionName = action.value();
                recordParams = action.recordParams();
                //构建操作记录实例对象
                operation = getInstance(request, operatorId, operatorName, moduleName, actionName, level, methodName);
            }
        }
        if (Objects.nonNull(operation)) {
            if (OperationConstant.GLOBAL_RECORD_PARAMS) {
                operation.setParams(handler.huntParameters(request, level, moduleName, actionName,args));
            } else if (!OperationConstant.GLOBAL_RECORD_PARAMS && recordParams) {
                operation.setParams(handler.huntParameters(request, level, moduleName, actionName,args));
            }
        }
        return operation;
    }

    @SneakyThrows
    private Operation getInstance(HttpServletRequest request, String operatorId, String operator, String module,
                                  String action, String level, String method) {
        Operation operation = (Operation) OperationConstant.ENTITY_CLASS.newInstance();
        operation.setOperatorId(operatorId);
        operation.setOperator(operator);
        operation.setAction(action);
        operation.setServerMethod(method);
        operation.setOperateLevel(level);
        operation.setModule(module);
        operation.setHost(handler.huntIp(request));
        operation.setOperateTime(LocalDateTime.now());
        operation.setRemark(OperationConstant.getValue());
        operation.setApplicationName(OperationConstant.APPLICATION_NAME);
        return operation;
    }

    @Around("advice()")
    public Object operateAspect(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        Throwable t = null;
        boolean flag = true;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            flag = false;
            t = e;
        }

        /**
         * 操作标识 代表操作成功或者失败
         */
        final boolean operateFlag = flag;
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attr.getRequest();

        Operation operation = createRecord(request, joinPoint);
        BiConsumer<HttpServletRequest, Operation<? extends Serializable>> consumer = handler.consume();

        if (Objects.nonNull(consumer)) {
            consumer.accept(request, operation);
        }
        OperationConstant.POOL.submit(() -> {
            try {
                //日志记录
                if (operation != null) {
                    operation.setOperateStatus(operateFlag);
                    operationService.insertOperation(operation);
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw Lombok.sneakyThrow(e);
            }
        });

        if (t != null) {
            throw t;
        }

        return result;
    }


    @Override
    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}
